package polytech.simulation.storage;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;

/**
 * Arbre permettant de gérer les B-Tree etc...
 *
 * @author Amirouche
 */
public class Tree {

	/**
	 * Description des positions possibles pour les noeud fils
	 */
	public enum Position {
		UP(1),
		MIDDLE(0),
		DOWN(-1);
		int value;
		private Position(int value) {
			this.value = value;
		}
	};
	
	/**
	 * Formes de branchements possibles
	 */
	public enum BranchingForm{
		A, B, C
	};
	
	/**
	 * Noeud de l'arbre
	 */
	public static class Node {

		/**
		 * Position du noeud
		 */
		private int i, j;
		
		/**
		 * Valeurs contenues dans le noeud
		 * { Nom de la donnée -> Valeur }
		 */
		private HashMap<String, Double> values;
		
		/**
		 * Noeud enfants du noeud courant
		 */
		private EnumMap<Position, Node> children;
		/**
		 * Probabilités des noeuds fils
		 */
		private EnumMap<Position, Double> probabilities;
		
		/**
		 * Noeuds parents du noeud courant
		 */
		private ArrayList<Node> parents;

		/**
		 * Constructeur par défaut
		 */
		public Node() {
			this.i				= 0;
			this.j				= 0;
			this.values			= new HashMap<String, Double>();
			this.children		= new EnumMap<Position, Node>(Position.class);
			this.probabilities	= new EnumMap<Position, Double>(Position.class);
			this.parents		= new ArrayList<Node>();
		}
		
		public Node(int i, int j){
			this();
			this.i = i;
			this.j = j;
		}
		
		public int getI() {
			return this.i;
		}
		
		public int getJ() {
			return this.j;
		}
		
		public Node setI(int i) {
			this.i = i;
			return this;
		}

		public Node setJ(int j) {
			this.j = j;
			return this;
		}

		public Node setValue(String id, double value) {
			this.values.put(id, value);
			return this;
		}
		
		public double getValue(String id){
			return this.values.get(id);
		}
		
		public double getProbability(Position position){
			return probabilities.get(position);
		}
		
		public Node getChild(Position position){
			return children.get(position);
		}
		
		/**
		 * Ajoute un noeud fils à la position indiquée et lui affecte la probabilité indiquée
		 * @param position		Position du fils
		 * @param node			Référence vers le noeud fils
		 * @param probability	Probabilité du fils
		 */
		public void setdChild(Position position, Node node, double probability){
			node.parents.add(this);
			this.children.put(position, node);	
			this.probabilities.put(position, probability);
		}

		public EnumMap<Position, Node> getChildren() {
			return children;
		}
		
		public Position getChildPosition(Node child){
			for(Position p : Position.values()){
				if(children.get(p).equals(child)){
					return p;
				}
			}
			return null;
		}
		
		/**
		 * Indique si le noeud courant est une feuille
		 * @return True si ce noeud est une fille, False sinon
		 */
		public boolean isLeaf(){
			return this.children.isEmpty();
		}
		
		/**
		 * Indique pour le noeud courant, le type de branchement des fils
		 * @return Type de branchement des noeuds fils
		 */
		public BranchingForm getBranchingForm(){
			if(isLeaf() || children.size() < 3){
				return null;
			}
			if(j == children.get(Position.MIDDLE).j){
				return BranchingForm.A;
			}
			if(j == children.get(Position.DOWN).j){
				return BranchingForm.B;
			}
			if(j == children.get(Position.UP).j){
				return BranchingForm.C;
			}
			return null;
		}

		public ArrayList<Node> getParents() {
			return parents;
		}
		
	}
	
	/**
	 * Noeud racine
	 */
	private Node root;

	/**
	 * Constructeur par défaut, initialise la racine
	 */
	public Tree() {
		this.root = new Node();
	}

	public Node getRoot() {
		return root;
	}
	
	/**
	 * Recherche du noeud correspondant aux i et j en paramètre
	 * @param i		Position en i désirée
	 * @param j		Position en j désirée
	 * @return		Noeud correspondant à la recherche, null si non trouvé
	 */
	public Node getNode(int i, int j){
		
		// On  lance la recherche à partir de la racine
		return getNode(i, j, this.root);
	}
	
	/**
	 * Recherche du noeud correspondant aux i et j en paramètre en commençant 
	 * la recherche depuis la position start
	 * @param i		Position en i désirée
	 * @param j		Position en j désirée
	 * @param start Noeud duquel commencer la recherche
	 * @return		Noeud correspondant à la recherche, null si non trouvé
	 */
	private Node getNode(int i, int j, Node start){
		
		// Si c'est le noeud courant, on le retourne
		if(start.getI() == i && start.getJ() == j){
			return start;
		}
		
		// Si on a atteint une feuille sans succès, on arrête
		// Si on a dépassé la profondeur limite, on arrête
		if(!start.isLeaf() && start.i <= i){
			
			// Sinon, on parcourt tous les fils
			for(Node n : start.getChildren().values()){

				// On réitère l'analyse sur les fils
				Node node = getNode(i, j, n);
				
				// Si un fils a retourné quelque chose de viable, on le retourne aussi
				if(node != null){
					return node;
				}
			}
		}
		
		// Null si échec total des itérations
		return null;
	}
}
